import numpy as np
import pandas as pd
from scipy.spatial.distance import cdist
from operator import itemgetter
from decimal import Decimal

from connect_database.music_tools import MusicTool
from connect_database.user_tools import UserTool


class CollaborativeFiltering:
    def __init__(self):
        self.ut = UserTool()
        self.user_id_list = self.ut.get_all_id()    # 获取用户所有id
        self.mt = MusicTool()
        self.music_id_list = self.mt.get_all_id()   # 获取音乐所有id
        self.data = self.convert_df()
        self.similarity = self.user_similarity()

    # 转换数据为DataFrame格式
    def convert_df(self):
        # 获取播放记录
        mt = UserTool()
        music_info = mt.get_user_record()
        # 最终存储的字典
        music_info_df = {}
        # 用户以及存在的数据
        for user_id, music_tuple in music_info.items():
            # 音乐索引
            music_one_list = [i * 0 for i in range(len(self.music_id_list))]
            for music_id, play_num in music_tuple:  # 获取音乐id与播放次数
                music_one_list[music_id - 1] = play_num
            music_info_df[str(user_id)] = music_one_list
        # 用户信息没有播放音乐，需要添加
        for i, user_id in enumerate(self.user_id_list):
            if str(user_id) not in music_info_df.keys():
                music_one_list = [i * 0 for i in range(len(self.music_id_list))]
                music_info_df[str(user_id)] = music_one_list
        data = pd.DataFrame(data=music_info_df,
                            index=self.music_id_list)

        data = data.T
        return data

    # 求解用户相似度
    def user_similarity(self):
        result = {}
        # 行索引，即用户
        users = self.data._stat_axis.values.tolist()
        # 对用户进行遍历
        for user in users:
            result[user] = {}
            for value in users:  # 嵌套循环，再次对用户进行遍历，计算不同用户之间的相似度
                if user == value:  # 用户相同，跳出最近循环
                    continue
                x = np.array(self.data.loc[user])  # 将series转数组
                y = np.array(self.data.loc[value])
                # 计算用户之间的余弦相似度
                result[user][value] = 1 - cdist(x.reshape(1, -1), y.reshape(1, -1), metric='cosine')[0][0]
                result[user][value] = float(Decimal(result[user][value]).quantize(Decimal("0.00")))
        return result

    # user的CF推荐算法
    def CF_recommend(self, user_id: str, front_num: int):
        rank = {}
        user_music = self.data.loc[user_id]  # 输入用户
        interacted_items = user_music[user_music > 0]  # 取用户u感兴趣的物品
        # 获取用户相似度的键值对，并按值降序排列
        for key, value in sorted(self.similarity[user_id].items(), key=itemgetter(1), reverse=True)[0: front_num]:
            v_item = self.data.loc[key]
            for i, rvi in v_item[v_item > 0].to_dict().items():
                if i in interacted_items:  # 若用户u和用户v均对物品i感兴趣，则跳出最近一层循环
                    continue
                # 如果用户u之前就对物品i感兴趣，获取原来感兴趣程度，若不感兴趣，则取0，再加上用户u和用户v之间的相似度*用户v对物品i的兴趣
                rank[i] = rank.get(i, 0) + value * rvi
        rank = sorted(rank.items(), key=itemgetter(1), reverse=True)
        return rank


if __name__ == '__main__':
    cf = CollaborativeFiltering()
    # # 选择和该用户感兴趣最相似的K个用户
    m = cf.user_similarity()
    rank_df = cf.CF_recommend("9", 3)
    print(rank_df)
